package lol.clann.minecraft.springboot.plugin.module.devtools.commands;

import com.ruoshui.utils.java.io.jar.JarUtils;
import com.ruoshui.utils.java.reflect.ReflectUtils;
import com.ruoshui.utils.json.JsonUtils;
import lol.clann.minecraft.springboot.api.annotation.command.Command;
import lol.clann.minecraft.springboot.api.annotation.command.CommandDefine;
import lol.clann.minecraft.springboot.api.annotation.command.Optional;
import lol.clann.minecraft.springboot.api.annotation.command.Sender;
import lol.clann.minecraft.springboot.api.bukkit.rawmessage.RawMessage;
import lol.clann.minecraft.springboot.api.bukkit.utils.*;
import lol.clann.minecraft.springboot.api.context.SpringContext;
import lol.clann.minecraft.springboot.api.model.core.ClassScanner;
import lol.clann.minecraft.springboot.plugin.module.devtools.Dumpper;
import lol.clann.minecraft.springboot.plugin.module.devtools.ListItemUtils;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.type.filter.AssignableTypeFilter;
import relocate.com.fasterxml.jackson.core.JsonProcessingException;
import relocate.com.fasterxml.jackson.databind.ObjectMapper;
import relocate.com.fasterxml.jackson.databind.ObjectWriter;

import javax.annotation.PostConstruct;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;

/**
 * @author pyz
 * @date 2019/5/20 10:40 PM
 */
@Slf4j
@CommandDefine(name = "devtools")
public class DevCommands implements Listener {
    @Autowired
    private ItemStackUtils itemStackUtils;
    @Autowired
    private BlockUtils blockUtils;
    @Autowired
    private EntityUtils entityUtils;
    @Autowired
    private ChunkUtils chunkUtils;
    @Autowired
    private TileEntityUtils tileEntityUtils;
    @Autowired
    private WorldUtils worldUtils;
    @Autowired
    private ServerUtils serverUtils;
    @Autowired
    private PlayerUtils playerUtils;

    private ObjectWriter objectWriter;

    @PostConstruct
    private void init() {
        objectWriter = SpringContext.getBean(ObjectMapper.class).writerWithDefaultPrettyPrinter();
    }

    @Command(showArgs = "showItem", des = "展示手里的物品", onlyPlayer = true)
    private void showItem(@Sender Player sender) throws Exception {
        ItemStack hand = sender.getItemInHand();
        if (itemStackUtils.isEmpty(hand)) {
            sender.sendMessage("请把物品拿在手上");
            return;
        }
        RawMessage
                .createRawMessage(itemStackUtils.getDisplayName(hand))
                .showItem(hand)
                .broadcast();
    }

    @Command(showArgs = "listPotionEffect", des = "列出所有PotionEffect")
    private boolean listPotionEffect(@Sender CommandSender sender) throws Exception {
        ListItemUtils.listLines(sender, "%s", 3, Arrays.stream(PotionEffectType.values()).filter(Objects::nonNull).map(i -> i.getId() + " " + i.getName()));
        return true;
    }

    @Command(showArgs = "listBiome", des = "列出所有生物群系")
    private boolean listBiome(@Sender CommandSender sender) throws Exception {
        ListItemUtils.listInSingleLine(sender, "%s", Arrays.stream(Biome.values()).filter(Objects::nonNull).map(i -> i.name()));
        return true;
    }

    @Command(showArgs = "listWorld", des = "列出所有世界,可以插件DIM编号")
    private boolean listWorld(@Sender CommandSender sender) throws Exception {
        ListItemUtils.listLines(sender, "%s", 3, Bukkit.getWorlds().stream().filter(Objects::nonNull).map(i -> "DIM" + worldUtils.getDimension(i) + " -> " + i.getName()));
        return true;
    }

    @Command(showArgs = "listSound", des = "列出所有Sound")
    private boolean listSound(@Sender CommandSender sender) throws Exception {
        ListItemUtils.listLines(sender, "%s", 20, Arrays.stream(Sound.values()).filter(Objects::nonNull).map(i -> i.name()));
        return true;
    }

    @Command(showArgs = "listEffect", des = "列出所有Effect")
    private boolean listEffect(@Sender CommandSender sender) throws Exception {
        ListItemUtils.listInSingleLine(sender, "%s", Arrays.stream(Effect.values()).filter(Objects::nonNull).map(i -> i.name()));
        return true;
    }

    @Command(showArgs = "location [<player>]", des = "获取自己/指定玩家的位置")
    private boolean location(@Sender CommandSender sender, @Optional Player player) throws Exception {
        if (player == null) {
            if (sender instanceof Player) {
                player = (Player) sender;
            } else {
                sender.sendMessage("缺少参数<player>");
                return false;
            }
        }
        Location loc = player.getLocation();
        sender.sendMessage(player.getName() + ":" + loc.toString());
        return true;
    }

    @Command(showArgs = "findSubClasses <basePackages> <className>", des = "查找所有子类")
    private void findSubClasses(@Sender CommandSender sender, String basePackages, Class clas) throws Exception {
        ClassScanner scanner = new ClassScanner(SpringContext.getApplicationEnvironment());
        scanner.addIncludeFilter(new AssignableTypeFilter(clas));
        String[] items = basePackages.split(",");
        Set<String> classNames = scanner.doScan(items);
        for (String className : classNames) {
            sender.sendMessage(className);
        }
        sender.sendMessage("find " + classNames.size() + "个匹配项");
    }

    @Command(showArgs = "getClassJar <className>", des = "获取指定类所在jar")
    private void getClassJar(@Sender CommandSender sender, Class clas) throws Exception {
        Path jar = JarUtils.getJar(clas);
        sender.sendMessage(String.valueOf(jar));
    }

    @Command(showArgs = "showClassFields <className>", des = "展示类中的字段")
    private void showClassFields(@Sender CommandSender sender, Class clas) throws ClassNotFoundException {
        for (Field item : ReflectUtils.getAllFields(clas)) {
            sender.sendMessage(String.valueOf(item));
        }
    }

    @Command(showArgs = "showClassMethods <className>", des = "展示类中的方法")
    private void showClassMethods(@Sender CommandSender sender, Class clas) throws ClassNotFoundException {
        for (Method item : ReflectUtils.getAllMethods(clas)) {
            sender.sendMessage(String.valueOf(item));
        }
    }

    @Command(showArgs = "itemInfo", onlyPlayer = true, des = "展示手里物品的信息")
    private void itemInfo(@Sender Player player) throws JsonProcessingException {
        ItemStack itemStack = player.getItemInHand();
        if (itemStack == null || itemStack.getType() == Material.AIR) {
            player.sendMessage("请把物品拿在手上");
            return;
        }
        Object nbt = itemStackUtils.toNBTTagCompound(itemStack);
        String json = objectWriter.writeValueAsString(nbt);
        player.sendMessage(json);
        player.sendMessage("material:" + itemStack.getType().name());
        player.sendMessage("nmsClass:" + itemStackUtils.toNMS(itemStack).getClass().getName());
        player.sendMessage("displayName:" + itemStackUtils.getDisplayName(itemStack));
        Bukkit.getConsoleSender().sendMessage(json);
    }

    @Command(showArgs = "dumpEntity", des = "导出所有entity信息")
    private void dumpEntity() throws Exception {
        try (Dumpper dumpper = new Dumpper(new File("dump/entity.txt"))) {
            for (World world : Bukkit.getWorlds()) {
                for (Entity entity : world.getEntities()) {
                    Object nmsEntity = entityUtils.toNMS(entity);
                    dumpper.print(String.format("%s,%s,%s,%s,%s", nmsEntity.getClass().getName(), world.getName(), entity.getLocation().getBlockX(), entity.getLocation().getBlockY(), entity.getLocation().getBlockZ()));
                    dumpper.newLine();
                }
            }
        }
    }

    @Command(showArgs = "dumpTileEntity", des = "导出所有tileEntity信息")
    private void dumpTileEntity() throws Exception {
        try (Dumpper dumpper = new Dumpper(new File("dump/tileEntity.txt"))) {
            for (World world : Bukkit.getWorlds()) {
                for (Chunk chunk : world.getLoadedChunks()) {
                    Collection<Object> tes = chunkUtils.getTileEntities(chunk);
                    for (Object te : tes) {
                        Location loc = tileEntityUtils.getLocation(te);
                        dumpper.print(String.format("%s,%s,%s,%s,%s", te.getClass().getName(), world.getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()));
                        dumpper.newLine();
                    }
                }
            }
        }
    }


    private BukkitTask debugTargetEntity;

    @Command(onlyPlayer = true, showArgs = "debugTargetEntity <distance>", des = "开启/关闭获取准星实体的调试功能")
    private void debugTargetEntity(@Sender Player player, float distance) throws Exception {
        if (debugTargetEntity != null) {
            debugTargetEntity.cancel();
            debugTargetEntity = null;
            player.sendMessage("关闭获取准星实体的调试功能");
            return;
        }
        debugTargetEntity = serverUtils.runTaskTimerAsynchronously(() -> {
            Entity target = entityUtils.getTargetEntity(player, distance);
            player.sendMessage(String.valueOf(target));
        }, 1, 5);
        player.sendMessage("开启获取准星实体的调试功能");
    }

    private boolean debugEntity = false;

    @Command(showArgs = "debugEntity", des = "开启/关闭 实体调试")
    private void debugEntity(@Sender Player player) throws JsonProcessingException {
        debugEntity = !debugEntity;
        player.sendMessage("实体调试:" + debugEntity);
    }

    private boolean debugBlock = false;

    @Command(showArgs = "debugBlock", des = "开启/关闭 方块调试")
    private void debugBlock(@Sender Player player) throws JsonProcessingException {
        debugBlock = !debugBlock;
        player.sendMessage("方块调试:" + debugBlock);
    }

    @Command(showArgs = "showPlayerData", des = "查看指定玩家数据(可查看离线玩家的)")
    private void showPlayerData(@Sender CommandSender sender, String target) throws JsonProcessingException {
        OfflinePlayer off = Bukkit.getOfflinePlayer(target);
        if (off == null) {
            sender.sendMessage("该玩家不存在:" + target);
            return;
        }

        Player p = playerUtils.toOnlinePlayer(off);
        Object nbt = entityUtils.toNBTTagCompound(p);
        String json = JsonUtils.writeValueAsString(nbt);
        LOGGER.info("玩家的数据:" + target);
        LOGGER.info(json);
        sender.sendMessage(json);
        sender.sendMessage("数据量可能较大,如需拷贝,请前往控制台.");
    }

    @Command(showArgs = "updateInventory [target]", des = "刷新指定玩家/自己背包")
    private void updateInventory(@Sender CommandSender sender, @Optional Player target) throws JsonProcessingException {
        if (target == null) {
            if (!(sender instanceof Player)) {
                sender.sendMessage("你不是玩家.");
                return;
            }
            target = (Player) sender;
        }
        target.updateInventory();
        sender.sendMessage(String.format("update %s's inventory", target.getName()));
    }

    @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
    private void onEvent(PlayerInteractEvent event) throws JsonProcessingException {
        if (!debugBlock || !event.getPlayer().isOp()) {
            return;
        }
        Block target = event.getClickedBlock();
        if (blockUtils.isEmpty(target)) {
            return;
        }
        event.setCancelled(true);
        Object nbt = blockUtils.toNBTTagCompound(target);
        String json = objectWriter.writeValueAsString(nbt);
        Object te = worldUtils.getTileEntity(target);
        event.getPlayer().sendMessage(json);
        event.getPlayer().sendMessage("material:" + target.getType().name());
        event.getPlayer().sendMessage("nmsClass:" + (te == null ? null : te.getClass().getName()));
        Bukkit.getConsoleSender().sendMessage(json);
    }

    @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
    private void onEvent(PlayerInteractEntityEvent event) throws JsonProcessingException {
        if (!debugEntity || !event.getPlayer().isOp()) {
            return;
        }
        Entity target = event.getRightClicked();
        if (target == null) {
            return;
        }
        event.setCancelled(true);
        Object nbt = entityUtils.toNBTTagCompound(target);
        String json = objectWriter.writeValueAsString(nbt);
        event.getPlayer().sendMessage(json);
        event.getPlayer().sendMessage("entityType:" + target.getType().name());
        event.getPlayer().sendMessage("nmsClass:" + entityUtils.toNMS(target).getClass().getName());
        Bukkit.getConsoleSender().sendMessage(json);
    }
}
